#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>

#include <linux/ioctl.h>
#include <linux/uaccess.h>

#include <linux/proc_fs.h>
#include <linux/jiffies.h>
#include <asm/uaccess.h>
#include <linux/seq_file.h>

#include "vser.h"

#define VSER_MAJOR    256
#define VSER_MINOR    0
#define VSER_DEV_CNT  1
#define VSER_DEV_NAME "vser"

struct vser_dev {
    unsigned int baud;
    struct option opt;
    struct cdev cdev;

    struct proc_dir_entry *pdir;
    struct proc_dir_entry *pdat;
};

DEFINE_KFIFO(vsfifo, char, 1024);
static struct vser_dev vsdev;

static int dat_show(struct seq_file* m, void *v)
{
    struct vser_dev *dev = m->private;
    seq_printf(m, "baudrate: %d\n", dev->baud);
    seq_printf(m, "frame format: %d%c%d\n", dev->opt.datab, (dev->opt.parity == 0 ? 'N' : dev->opt.parity == 1 ? 'O' : 'E'), dev->opt.stopb);
    return 0;
}

static int proc_open(struct inode* inode, struct file *file)
{
    return single_open(file, dat_show, PDE_DATA(inode));
}

static struct file_operations proc_ops = {
    .owner = THIS_MODULE,
    .open = proc_open,
    .release = single_release,
    .read = seq_read,
    .llseek = seq_lseek,
};

static int __init vser_init(void)
{
    int ret;
    dev_t dev;

    dev = MKDEV(VSER_MAJOR, VSER_MINOR);
    ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
    if (ret)
        goto reg_err;

    cdev_init(&vsdev.cdev, &proc_ops);
    vsdev.cdev.owner = THIS_MODULE;
    vsdev.baud = 115200;
    vsdev.opt.datab = 8;
    vsdev.opt.parity = 0;
    vsdev.opt.stopb = 1;

    ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);
    if (ret)
        goto add_err;

    vsdev.pdir = proc_mkdir("vser", NULL);  // 创建vser目录
    if (!vsdev.pdir)
        goto dir_err;
    vsdev.pdat = proc_create_data("info", 0, vsdev.pdir, &proc_ops, &vsdev);  // 创建info文件
    if (!vsdev.pdat)
        goto dat_err;

    return 0;

dat_err:
    remove_proc_entry("vser", NULL);
dir_err:
    cdev_del(&vsdev.cdev);
add_err:
    unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
    return ret;
}

static void __exit vser_exit(void)
{
    dev_t dev;
    dev = MKDEV(VSER_MAJOR, VSER_MINOR);

    remove_proc_entry("info", vsdev.pdir);
    remove_proc_entry("vser", NULL);

    cdev_del(&vsdev.cdev);
    unregister_chrdev_region(dev, VSER_DEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("KDY <chaoxi_todo@qq.com>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");